#pragma once
#include <iostream>
#include <locale>
#include "Utils.h"

class Map
{
public:
  Map(char* squares)
    : _squares(squares)
  {
    //std::cout << "Map " << (void*)_squares << std::endl;
    FindR();
  }

  ~Map()
  {
    //std::cout << "~Map " << (void*)_squares << std::endl;
    delete[] _squares;
  }

  Map* copy() const
  {
    char* other = Utils::copy(_squares, nColumns * nRows);
    return new Map(other);
  }

  const char* operator[] (int i) const
  {
    if(!(i >= 0 && i < nRows))
    {
      std::cout << i  << " Nombre de lignes " << nRows << std::endl;
    }

    // Get the ith row
    assert(i >= 0 && i < nRows);
    return &_squares[i * nColumns];
  }

  char* operator[] (int i)
  {
    // Get the ith row
    if(!(i >= 0 && i < nRows))
    {
      std::cout << i  << " Nombre de lignes " << nRows << std::endl;
    }

    assert(i >= 0 && i < nRows);
    return &_squares[i * nColumns];
  }

  friend std::ostream& operator<<(std::ostream& out, const Map& map)
  {
    for (int i = map.nRows -1 ; 0 <= i; --i)
    {
      for (int j = 0; j < map.nColumns; ++j)
      {
        out << map[i][j];
      }
      out << std::endl;
    }
    return out;
  }

  void update()
  {
    Map* mapTemp =  this->copy();
    bool noLampdasRemaining = true;
    for (int i =  0; i < nRows; ++i)
    {
      for (int j = 0; j < nColumns; ++j)
      {
        if ((*mapTemp)[i][j] == '\\')
        {
          noLampdasRemaining = false;
        }
      }
    }

    for (int x = 0; x < nRows; ++x)
    {
      for (int y = 0; y < nColumns; ++y)
      {
        if ((*mapTemp)[y][x] == 'L' && noLampdasRemaining)
        {
          (*this)[y][x] = 'O';
        }
      }
    }


    for (int y = 1; y < nRows; ++y)
    {
      for (int x = 0; x < nColumns - 1; ++x)
      {
        if ((*mapTemp)[y][x] == '*' && (*mapTemp)[y - 1][x] == ' ')
        {
          (*this)[y][x] = ' ';
          (*this)[y - 1][x] = '*';
        }
        if ((*mapTemp)[y][x] == '*' && (*mapTemp)[y - 1][x] == '*' && (*mapTemp)[y][x + 1] == ' ' &&
          (*mapTemp)[y - 1][x + 1] == ' ')
        {
          (*this)[y][x] = ' ';
          (*this)[y - 1][x + 1] = '*';
        }
        if ((*mapTemp)[y][x] == '*' && (*mapTemp)[y - 1][x] == '*' &&
          ((*mapTemp)[y][x + 1] != ' ' || (*mapTemp)[y - 1][x + 1] != ' ') && (*mapTemp)[y][x - 1] == ' ' &&
          (*mapTemp)[y - 1][x - 1] == ' ')
        {
          (*this)[y][x] = ' ';
          (*this)[y - 1][x - 1] = '*';
        }
        if ((*mapTemp)[y][x] == '*' && (*mapTemp)[y - 1][x] == '\\' && (*mapTemp)[y][x + 1] == ' ' &&
          (*mapTemp)[y - 1][x + 1] == ' ')
        {
          (*this)[y][x] = ' ';
          (*this)[y - 1][x + 1] = '*';
        }
      }
    }

    for (int y = 1; y < nRows; y++)
    {
      int x = nColumns - 1;
      if ((*mapTemp)[y][x] == '*' && (*mapTemp)[y - 1][x] == ' ')
      {
        (*this)[y][x] = ' ';
        (*this)[y - 1][x] = '*';
      }
    }
  }

  void FindR()
  {
    xR = -1;
    yR = -1;
    for (int i =  0; i < nRows; ++i)
    {
      for (int j = 0; j < nColumns; ++j)
      {
        if ((*this)[i][j] == 'R')
        {
          xR = j;
          yR = i;
          break;
        }
      }
    }

  }

  void DisplayInfos()
  {
    std::cout << "xR :" << xR << std::endl ;
    std::cout << "yR :" << yR << std::endl ;
    std::cout << "Nombre de colonnes : " << nColumns << std::endl; 
    std::cout << "Nombre de lignes: : " << nRows << std::endl; 
  }

  char* const _squares;
  int xR;
  int yR;

  static int nRows;
  static int nColumns;
};

class MapHash : public std::unary_function<const Map*, size_t>
{
public:
  MapHash()
    : _collate(std::use_facet<std::collate<char> >(std::locale()))
  {
  }
  size_t operator()(const Map* map) const
  {
    return _collate.hash(map->_squares, map->_squares + Map::nRows * Map::nColumns);
  }
private:
  const std::collate<char>& _collate;
};

class MapEqualTo : public std::binary_function<const Map*, const Map*, bool>
{
public:
  bool operator()(const Map* map1, const Map* map2) const
  {
    // TODO: try _collate.compare
    return memcmp(map1->_squares, map2->_squares, Map::nRows * Map::nColumns * sizeof(char)) == 0;
  }
};

class MapCompare : public std::binary_function<const Map*, const Map*, bool>
{
public:
  bool operator()(const Map* map1, const Map* map2) const
  {
    // TODO: try _collate.compare
    return memcmp(map1->_squares, map2->_squares, Map::nRows * Map::nColumns * sizeof(char)) < 0;
  }
};